//@author A0105740R
package rightnow.parser.cmdline;

import java.util.LinkedList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import rightnow.storage.Task;

/**
 * <p>This parser parses a string to a task.</p>
 * <p>Basic formats:<br>
 * < description > at < time > on < date > by < time > on < date ><br>
 * "< description >" at < time > on < date > by < time > on < date ></p>
 * <p>Implicit formats:<br>
 * < description > at < time > [on < date >] by < time > [on < date >]<br>
 * < description > at < time > [on < date >]<br>
 * < description > starting < date ><br>
 * < description > by < time > [on < date >]<br>
 * < description > by < date ></p>
 * <p>Rules:<br>
 * 1. Keywords "at" and "starting" must be put before "by"<br>
 * 2. Task description should not be empty.<br>
 * 3. Use quotes to force parser parse date and time as text.</p>
 * 
 * <p>This class also uses singleton design pattern to avoid multiple copies.</p>
 */
enum CmdTaskParser {
	INSTANCE;
	
	private static final String TRACE_START_DATE_TIME = "Start date time: %s";
	private static final String TRACE_END_DATE_TIME = "End date time: %s";

	private static final Logger log = 
			LoggerFactory.getLogger(CmdTaskParser.class);
	
	private static final char TEXT_DELIM = '"';
	
	private LinkedList<Token> tokenList = null;
	private boolean isStrict;
	
	/**
	 * @param str != null
	 * @return the parsed Task
	 * @throws CmdParserException
	 */
	public Task parse(String str) throws CmdParserException {	
		tokenList = new LinkedList<Token>();
		
		str = str.trim();
		if (str.isEmpty()) {
			throw new CmdParserException(CmdParserErrorMsg.ERROR_EMPTY_TASK_TITLE);
		}
		extractText(str);
		if (tokenList.size() == 1 
				&& tokenList.get(0).getTokenType() == TokenType.TEXT) {
			return new Task(tokenList.get(0).getTokenString());
		}
		
		CmdTaskParserCore taskParser = new CmdTaskParserCore(tokenList);
		taskParser.markIdtfTimeDate();
		taskParser.elimExtraIdtf();
		taskParser.markUndtAsText();
		
		if (isStrict) {
			if (taskParser.hasExtraText()) {
				throw new CmdParserException(CmdParserErrorMsg.ERROR_EXTRA_TEXT);
			}
		}
		else {
			if (!taskParser.hasText()) {
				throw new CmdParserException(
						CmdParserErrorMsg.ERROR_EMPTY_TASK_TITLE);
			}
			if (!taskParser.isIdtfAllAtBack()) {
				throw new CmdParserException(
						CmdParserErrorMsg.ERROR_IDTF_NOT_AT_BACK);
			}
		}
		
		if (!taskParser.hasValidNumIdtf()) {
			throw new CmdParserException(
					CmdParserErrorMsg.ERROR_INVALID_NUM_IDTF);
		}
		
		if (!taskParser.isStartIdtfBeforeEndIdtf()) {
			throw new CmdParserException(
					CmdParserErrorMsg.ERROR_START_IDTF_AFTER_END_IDTF);
		}
		
		taskParser.determineTime();
		
		if (!taskParser.hasValidNumUndetDates()) {
			throw new CmdParserException(
					CmdParserErrorMsg.ERROR_INVALID_NUM_DATES);
		}		
		if (!taskParser.isValidPositionUndetDate()) {
			throw new CmdParserException(
					CmdParserErrorMsg.ERROR_INVALID_POSITION_DATES);
		}
		
		taskParser.determineDate();
		if (!taskParser.isParsingSuccessful()) {
			throw new CmdParserException(CmdParserErrorMsg.ERROR_DATE_PARSING);
		}
		
		taskParser.adjustDate();
		
		taskParser.determineImplicitDateAndTime();
		
		if (!taskParser.isValidStartEndDateTime()) {
			throw new CmdParserException(
					CmdParserErrorMsg.ERROR_INVALID_START_END_DATETIME);
		}
		
		Task result = taskParser.getResult();
		log.trace(String.format(TRACE_START_DATE_TIME, 
				result.getStart() != null? result.getStart().toString() : null));
		log.trace(String.format(TRACE_END_DATE_TIME,
				result.getEnd() != null? result.getEnd().toString() : null));
		
		return result;
	}
	
	/**
	 * @param str != null
	 * @throws CmdParserException
	 */
	private void extractText(String str) throws CmdParserException {
		if (str.charAt(0) == TEXT_DELIM) {
			isStrict = true;
			int textEnd = str.lastIndexOf(TEXT_DELIM);
			if (textEnd == 0) {
				throw new CmdParserException(
						CmdParserErrorMsg.ERROR_MISSING_CLOSE_QUOTE);
			}
			String text = str.substring(1, textEnd);
			text = text.trim();
			if (text.isEmpty()) {
				throw new CmdParserException(
						CmdParserErrorMsg.ERROR_EMPTY_TASK_TITLE);
			}
			tokenList.add(new Token(text,TokenType.TEXT));
			String sub = str.substring(textEnd + 1).trim();
			if (!sub.isEmpty()) {
				tokenList.add(new Token(sub, TokenType.UNDET));
			}
		}
		else {
			isStrict = false;
			tokenList.add(new Token(str, TokenType.UNDET));
		}
	}
}
